1T_Decorator

  • Decorator - function 기반으로 구현했었고 리펙토링 하면서 class 기반의 데코레이터로 변경할 것이다.
  • 클래스 기반으로 변경하면 장점: decorator 자체도 "모듈화" 할 수 있다. 의미 있는 데이터와, 메쏘드들로 묶을 수 있다.

In [4]:
import time

def timer(function):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = function(*args, **kwargs)
        end_time = time.time()
        print("{time}s".format(time=end_time-start_time))
    return wrapper
# 새로운 함수를 만들고, 그 함수를 return 하는 함수 => decorator 함수
# 새롭게 만들어진 wrapper라는 함수가 사실은 실행이 된다.

In [5]:
@timer
def print_hello():
    print("hello world")

In [6]:
print_hello()
#print_hello 라는 이름을 가지고 있기는 하지만, 사실상의 timer->wrapper function이 실행


hello world
0.0s

In [7]:
def print_goodbye():
    print("hello world_2")

wrapper = timer(print_goodbye)

In [8]:
wrapper()
#위에거랑 완전히 동일한 형태이다.
# 사실상 decorator 는 기존의 정의된 함수를 한번 묶는 것과 100% 동일하다.


hello world_2
0.0s

In [10]:
# args, kwargs
def hello_items(**kwargs):
# 함수를 정의하는 시점에 사용하는 kwargs ( **kwargs ) => packing
# 즉, 여러 개로 들어온 값을 kwargs라는 dict로 하나로 묶은 것이다. 그래서 packing
    for key, value in kwargs.items():
        print("{key} => {value}".format(key=key, value=value))

In [11]:
hello_items(name="kkp", age=29)


age => 29
name => kkp

In [12]:
information = {"name": "poy", "age": 28}
hello_items(information)


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-12-035457999298> in <module>()
      1 information = {"name": "poy", "age": 28}
----> 2 hello_items(information)

TypeError: hello_items() takes 0 positional arguments but 1 was given

In [13]:
information = {"name": "poy", "age": 28}
hello_items(**information)  # dict => key, value => unpacking
# 함수를 호출하는 시점에서 사용하는 kwargs ( **kwargs )


age => 28
name => poy

In [14]:
@timer
def print_student_info(name, course, age):
    pass

In [15]:
print_student_info("kimkipyo", "dss", 29) # => *args => packing
# *args => ("kimkipyo", "dss", 29 ) (type은 tuple)


0.0s

In [16]:
import time
# print_student_info(name, course, age)
# args = ("dobestan", "wps", 24)  # packing
# print_student_info(*args)       # unpacking 
# 똑같이 생겼는데,
# 1. 함수 정의할 때랑 ( packing ),
# 2. 함수 호출할 때 ( unpacking )랑 반대의 기능이 기능

def timer(function):
    def wrapper(*args, **kwargs):  # packing => 함수 정의부
        print("args => {my_args}".format(my_args=args))
        print("kwargs => {my_kwargs}".format(my_kwargs=kwargs))
        
        start_time = time.time()
        result = function(*args, **kwargs)  # unpacking => tuple => 하나하나 / dict => key,value...
                                            # 함수 호출부
        end_time = time.time()
        print("{time}s".format(time=end_time-start_time))
        return result
    return wrapper

In [17]:
def hello():
    print("hello world")

In [18]:
hello()


hello world

In [19]:
def execute_three_times(function):
    for i in range(3):
        function()

In [20]:
execute_three_times(hello())


hello world
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-20-7fd8f2c9d2a5> in <module>()
----> 1 execute_three_times(hello())

<ipython-input-19-0892c1d321b3> in execute_three_times(function)
      1 def execute_three_times(function):
      2     for i in range(3):
----> 3         function()

TypeError: 'NoneType' object is not callable

In [21]:
execute_three_times(hello)
# 함수를 인자로 받는 것과 함수 결과값을 인자로 받는 것은 다르다.


hello world
hello world
hello world

In [22]:
def double(x):
    return x * 2

def triple(x):
    return x * 3

In [23]:
temp = double(4)
triple(temp) #마찬가지로 결과값이 들어가야 한다.


Out[23]:
24

In [24]:
# bold => <b>...</b>
# italic => <i>...</i>
# decorator 구현하기 2개
# 함수 아무거나 2개 구현하셔서

In [34]:
def bold(function):
    def wrapper(*args, **kwargs):
        return "<b>" + function(*args, **kwargs) + "</b>"
    return wrapper

def italic(function):
    def wrapper(*args, **kwargs):
        return "<i>" + function(*args, **kwargs) + "</i>"
    return wrapper

@bold
@italic
def return_hello():
    return "hello world"

def return_goodbye():
    return "good bye"

In [35]:
return_goodbye = bold(italic(return_goodbye))   # decorator 의 실제 기능 ( @... )
return_goodbye()


Out[35]:
'<b><i>good bye</i></b>'

In [36]:
return_hello()


Out[36]:
'<b><i>hello world</i></b>'

In [38]:
@italic
@bold
def return_goodbye():
    return "good bye"

return_goodbye()


Out[38]:
'<i><b>good bye</b></i>'

In [39]:
#함수 안에 함수를 만드는 사례
def return_double_function():
    def double(x):
        return x * 2
    return double

In [40]:
return_double_function()


Out[40]:
<function __main__.return_double_function.<locals>.double>

In [41]:
return_double_function()(10)


Out[41]:
20

In [42]:
def return_multiply_function(n):
    def multiply(x):
        return x * n
    return multiply

In [43]:
return_multiply_function(10)(20)


Out[43]:
200

In [44]:
double = return_multiply_function(2)
triple = return_multiply_function(3)
quadruple = return_multiply_function(4)

In [45]:
double(2)


Out[45]:
4

In [46]:
triple(4)


Out[46]:
12

In [47]:
quadruple(10)


Out[47]:
40

In [48]:
ten_times = return_multiply_function(10)  # 10을 곱해주는 함수

ten_times(200)


Out[48]:
2000

2T_Decorator를 클래스로 변경-Class Decorator


In [49]:
class Timer():
    
    def __init__(self, function):
        self.function = function

In [50]:
@Timer
def print_hello():
    print("hello world")
#함수와 클래스 구현이 100% 동일한 예제이다.
#print_hello = Timer(print_hello)   # 이것과 위의 과정과 동일한 표현이다.
# print_hello의 type은 Timer class로부터 만들어진 => Timer object 이다.

In [52]:
print_hello()  #오류의 이유는 timer 객체에다가 함수를 부르듯이 불렀기 때문에


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-52-bdb96ee6feed> in <module>()
----> 1 print_hello()  #오류의 이유는 timer 객체에다가 함수를 부르듯이 불렀기 때문에

TypeError: 'Timer' object is not callable

In [53]:
class Student():
    
    def __init__(self, name):
        self.name = name

In [54]:
student = Student("kkp")

In [56]:
student()  #마찬가지 이류로 오류가 난다.


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-56-0522124fd769> in <module>()
----> 1 student()  #마찬가지 이류로 오류가 난다.

TypeError: 'Student' object is not callable

In [57]:
class HelloWorld():
    
    def __init__(self):
        print("init")
         
    def __call__(self):   # 객체가 함수처럼 호출되면, `self()` 될 때, 실행된다.
        print("hello world")

In [58]:
HelloWorld()


init
Out[58]:
<__main__.HelloWorld at 0x87ee9b0>

In [59]:
helloworld = HelloWorld()


init

In [60]:
helloworld() #__call__ 함수 없었으면 오류가 난다.


hello world

In [62]:
dir(helloworld)   #메쏘드와 속성들을 볼 수 있다.


Out[62]:
['__call__',
 '__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__']
  • 상속 => 상위 클래스의 기능과(메쏘드) 값(속성)을 그대로 가져오기 위해서 => 기능의 확장
  • Decorator => 함수의 wrapper (함수가 실행되는 시점에 결과를 다르게 하고 싶거나 함수로 새로운 함수를 만들 때 데코레이터를 쓴다.)

In [63]:
# Memoization, 기존의 결과값을 어딘가에 저장을 해서
# Factorial ( n! = n * n-1 * ... * 2 * 1 ) 을 함수로 구현

cache = {}

def factorial(n):
    
    if n <= 1:
        result = cache[n] = 1
        return result
    
    if n in cache:
        return cache[n]
    
    result = n * factorial(n-1)
    cache[n] = result
    return result

In [66]:
factorial(8)


Out[66]:
40320

In [67]:
cache


Out[67]:
{1: 1, 2: 2, 3: 6, 4: 24, 5: 120, 6: 720, 7: 5040, 8: 40320}

In [72]:
class Factorial():
    
    def __init__(self):  # 클래스 객체가 초기화될 때 실행되는 메쏘드
        self.cache = {}  # factorial 에 대한 연산 결과를 저장하고 있는 dict 
    
    def __call__(self, n):  # 클래스 객체가 함수처럼 불릴 때 실행되는 메쏘드 ( `self(n)`, callable )
        if n <= 1:
            result = self.cache[n] = 1
            return result

        if n in self.cache:
            return self.cache[n]

        result = n * self.__call__(n-1)  # 결과를 계산하고, 그 결과를 result
        self.cache[n] = result   # self.cache => {1..., n: result}
        return result
    
    def __str__(self):   # 클래스 객체를 print 할 때 실행되는 메쏘드 ( `print(self)` )
        return "\n".join([
            "{key}! == {value}".format(key=key, value=value)
            for key, value
            in self.cache.items()
        ])

# Class Decorator => 구현할 수 있겠구나

In [73]:
factorial = Factorial()  #factorial이라는 클래스에 Factorial()이라는 객체를 만듦

In [74]:
factorial(5)


Out[74]:
120

In [75]:
factorial.cache


Out[75]:
{1: 1, 2: 2, 3: 6, 4: 24, 5: 120}

In [76]:
print(factorial)


1! == 1
2! == 2
3! == 6
4! == 24
5! == 120

In [77]:
"강아지" in ["강아지", "고양이", "이구아나"]


Out[77]:
True

In [78]:
"강아지" in {"강아지": "dog", "고양이": "cat"}


Out[78]:
True

In [80]:
"dog" in {"강아지": "dog", "고양이": "cat"}.values()


Out[80]:
True

In [81]:
{"강아지": "dog", "고양이": "cat"}.keys()


Out[81]:
dict_keys(['고양이', '강아지'])

In [82]:
{"강아지": "dog", "고양이": "cat"}.values()


Out[82]:
dict_values(['cat', 'dog'])

In [83]:
{"강아지": "dog", "고양이": "cat"}.items()


Out[83]:
dict_items([('고양이', 'cat'), ('강아지', 'dog')])

In [84]:
cache = {}

In [85]:
cache[1] = 1

In [86]:
cache


Out[86]:
{1: 1}

In [87]:
cache[2] = 2

In [89]:
cache


Out[89]:
{1: 1, 2: 2}

In [90]:
cache[0] = 10

In [91]:
cache


Out[91]:
{0: 10, 1: 1, 2: 2}

3T_decorator(class)


In [92]:
class Timer():
    def __init__(self, function):
        self.function = function

In [94]:
@Timer
def print_hello():
    print("hello world")

# print_hello == Timer(print_hello)
# print_hello() == Timer(print_hello)()  # Class => callable => __call__(self...)

In [95]:
print_hello()  #오류가 나기 때문에 __call__ 불러야지


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-95-706cf67589fb> in <module>()
----> 1 print_hello()  #오류가 나기 때문에 __call__ 불러야지

TypeError: 'Timer' object is not callable

In [96]:
class Timer():
    def __init__(self, function):
        self.function = function
        
    def __call__(self):
        result = self.function()
        return result

In [98]:
@Timer
def print_hello():
    print("hello world")

In [99]:
print_hello()


hello world

In [109]:
import time


class Timer():
    
    def __init__(self, function):
        self.function = function
        
    def __call__(self):
        # __call__ => function decorator 에서의 wrapper 와 동일한 역할
        start_time = time.time()
        result = self.function()
        end_time = time.time()
        print("{time}s".format(time=end_time-start_time))
        return result

In [110]:
@Timer
def print_hello():
    print("hello world")

In [111]:
print_hello()


hello world
0.0s

In [112]:
@Timer
def print_hello(name):
    print("hello, " + name)

In [113]:
print_hello("kkp")   #오류가 난다. 그래서 args, kwarg를 넣어줘야 한다.


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-113-740bc77d71f3> in <module>()
----> 1 print_hello("kkp")   #오류가 난다. 그래서 args, kwarg를 넣어줘야 한다.

TypeError: __call__() takes 1 positional argument but 2 were given

In [114]:
import time


class Timer():
    
    def __init__(self, function):
        self.function = function
        
    def __call__(self, *args, **kwargs):
        start_time = time.time()
        result = self.function(*args, **kwargs)
        end_time = time.time()
        print("{time}s".format(time=end_time-start_time))
        return result

In [115]:
@Timer
def print_hello(name):
    print("hello, " + name)

In [116]:
print_hello("kkp")


hello, kkp
0.0s

In [117]:
import time


class Timer():
    
    def __init__(self, function):
        self.function = function
        
    def __call__(self, *args, **kwargs):
        print(args)
        print(kwargs)
        start_time = time.time()
        result = self.function(*args, **kwargs)
        end_time = time.time()
        print("{time}s".format(time=end_time-start_time))
        return result

In [118]:
def print_name_and_age(**kwargs):
    for key, value in kwargs.items():
        print("{key} => {value}".format(key=key, value=value))
#일반적인 방법

In [119]:
print_name_and_age(name="kkp", age=29)


age => 29
name => kkp

In [120]:
def print_name_and_age(**kwargs):
    if kwargs.get("name"):   #kwargs에 name이 있었으면
        print("name => {name}".format(name=kwargs.get("name")))
        
    if kwargs.get("age"):   #kwargs에 age가 있었으면
        print("나이도 입력받았습니다.")

In [121]:
print_name_and_age(name="kkp")


name => kkp

In [122]:
print_name_and_age(age=29)


나이도 입력받았습니다.

In [123]:
print_name_and_age(name="kkp", age=29)


name => kkp
나이도 입력받았습니다.

In [ ]:


In [124]:
class Tagify():
    #<b><i>...</i></b>
    def __init__(self, function):
        self.function = function
        
    def __call__(self, *args, **kwargs):
        boldify = "<b>" + self.function(*args, **kwargs) + "</b>"
        italicfy = "<i>" + boldify + "</i>"
        return italicfy

In [125]:
@Tagify
def print_hello():
    return "hello world"

print_hello()


Out[125]:
'<i><b>hello world</b></i>'

In [126]:
class Tagify():
    # <p><b><i>...</i></b></p>
    
    def __init__(self, function):
        self.function = function
        
    def __call__(self, *args, **kwargs):
        paragraphify = self.tagify("p", self.function(*args, **kwargs))
        boldify = self.tagify("b", paragraphify)
        italicfy = self.tagify("i", boldify)
        return italicfy
    
    def tagify(self, tag, text):
        return "<{tag}>{text}</{tag}>".format(
            tag=tag,
            text=text,
        )

In [127]:
@Tagify
def print_hello():
    return "hello world"

print_hello()


Out[127]:
'<i><b><p>hello world</p></b></i>'